<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>The source code</title>
  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  <style type="text/css">
    .highlight { display: block; background-color: #ddd; }
  </style>
  <script type="text/javascript">
    function highlight() {
      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
    }
  </script>
</head>
<body onload="prettyPrint(); highlight();">
  <pre class="prettyprint lang-js">var matrixUtil = require(&quot;../utils/affine_matrix_util&quot;);

var textUtil = require(&quot;../utils/text_util&quot;);

var PathProxy = require(&quot;../graphic/PathProxy&quot;);

var BoundingRect = require(&quot;../graphic/BoundingRect&quot;);

var Text = require(&quot;../graphic/Text&quot;);

var _core = require(&quot;./core&quot;);

var createElement = _core.createElement;

var _constants = require(&quot;../utils/constants&quot;);

var PI = _constants.PI;
var PI2 = _constants.PI2;
var mathRound = _constants.mathRound;
var mathAbs = _constants.mathAbs;
var mathCos = _constants.mathCos;
var mathSin = _constants.mathSin;
// TODO
// 1. shadow
// 2. Image: sx, sy, sw, sh
var CMD = PathProxy.CMD;
var NONE = &#39;none&#39;;
var degree = 180 / PI;
var EPSILON = 1e-4;

function round4(val) {
  return mathRound(val * 1e4) / 1e4;
}

function isAroundZero(val) {
  return val &lt; EPSILON &amp;&amp; val &gt; -EPSILON;
}

function pathHasFill(style, isText) {
  var fill = isText ? style.textFill : style.fill;
  return fill != null &amp;&amp; fill !== NONE;
}

function pathHasStroke(style, isText) {
  var stroke = isText ? style.textStroke : style.stroke;
  return stroke != null &amp;&amp; stroke !== NONE;
}

function applyTransform(svgEl, m) {
  if (m) {
    attr(svgEl, &#39;transform&#39;, &#39;matrix(&#39; + Array.prototype.join.call(m, &#39;,&#39;) + &#39;)&#39;);
  }
}

function attr(el, key, val) {
  if (!val || val.type !== &#39;linear&#39; &amp;&amp; val.type !== &#39;radial&#39;) {
    // Don&#39;t set attribute for gradient, since it need new dom nodes
    el.setAttribute(key, val);
  }
}

function attrXLink(el, key, val) {
  el.setAttributeNS(&#39;http://www.w3.org/1999/xlink&#39;, key, val);
}

function bindStyle(svgEl, style, isText, el) {
  if (pathHasFill(style, isText)) {
    var fill = isText ? style.textFill : style.fill;
    fill = fill === &#39;transparent&#39; ? NONE : fill;
    attr(svgEl, &#39;fill&#39;, fill);
    attr(svgEl, &#39;fill-opacity&#39;, style.fillOpacity != null ? style.fillOpacity * style.opacity : style.opacity);
  } else {
    attr(svgEl, &#39;fill&#39;, NONE);
  }

  if (pathHasStroke(style, isText)) {
    var stroke = isText ? style.textStroke : style.stroke;
    stroke = stroke === &#39;transparent&#39; ? NONE : stroke;
    attr(svgEl, &#39;stroke&#39;, stroke);
    var strokeWidth = isText ? style.textStrokeWidth : style.lineWidth;
    var strokeScale = !isText &amp;&amp; style.strokeNoScale ? el.getLineScale() : 1;
    attr(svgEl, &#39;stroke-width&#39;, strokeWidth / strokeScale); // stroke then fill for text; fill then stroke for others

    attr(svgEl, &#39;paint-order&#39;, isText ? &#39;stroke&#39; : &#39;fill&#39;);
    attr(svgEl, &#39;stroke-opacity&#39;, style.strokeOpacity != null ? style.strokeOpacity : style.opacity);
    var lineDash = style.lineDash;

    if (lineDash) {
      attr(svgEl, &#39;stroke-dasharray&#39;, style.lineDash.join(&#39;,&#39;));
      attr(svgEl, &#39;stroke-dashoffset&#39;, mathRound(style.lineDashOffset || 0));
    } else {
      attr(svgEl, &#39;stroke-dasharray&#39;, &#39;&#39;);
    } // PENDING


    style.lineCap &amp;&amp; attr(svgEl, &#39;stroke-linecap&#39;, style.lineCap);
    style.lineJoin &amp;&amp; attr(svgEl, &#39;stroke-linejoin&#39;, style.lineJoin);
    style.miterLimit &amp;&amp; attr(svgEl, &#39;stroke-miterlimit&#39;, style.miterLimit);
  } else {
    attr(svgEl, &#39;stroke&#39;, NONE);
  }
}
<span id='qrenderer-svg-SVGPath'>/**
</span> * @class qrenderer.svg.SVGPath
 * 
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */


function pathDataToString(path) {
  var str = [];
  var data = path.data;
  var dataLength = path.len();
  var cmd = 0;
  var cmdStr = &#39;&#39;;
  var nData = 0;
  var cx = 0;
  var cy = 0;
  var rx = 0;
  var ry = 0;
  var theta = 0;
  var dTheta = 0;
  var psi = 0;
  var clockwise = 0;
  var dThetaPositive = 0;
  var isCircle = false;
  var unifiedTheta = 0;
  var large = false;
  var x = 0;
  var y = 0;
  var w = 0;
  var h = 0;
  var x0 = 0;
  var y0 = 0;

  for (var i = 0; i &lt; dataLength;) {
    cmd = data[i++];
    cmdStr = &#39;&#39;;
    nData = 0;

    switch (cmd) {
      case CMD.M:
        cmdStr = &#39;M&#39;;
        nData = 2;
        break;

      case CMD.L:
        cmdStr = &#39;L&#39;;
        nData = 2;
        break;

      case CMD.Q:
        cmdStr = &#39;Q&#39;;
        nData = 4;
        break;

      case CMD.C:
        cmdStr = &#39;C&#39;;
        nData = 6;
        break;

      case CMD.A:
        cx = data[i++];
        cy = data[i++];
        rx = data[i++];
        ry = data[i++];
        theta = data[i++];
        dTheta = data[i++];
        psi = data[i++];
        clockwise = data[i++];
        dThetaPositive = mathAbs(dTheta);
        isCircle = isAroundZero(dThetaPositive - PI2) || (clockwise ? dTheta &gt;= PI2 : -dTheta &gt;= PI2); // Mapping to 0~2PI

        unifiedTheta = dTheta &gt; 0 ? dTheta % PI2 : dTheta % PI2 + PI2;
        large = false;

        if (isCircle) {
          large = true;
        } else if (isAroundZero(dThetaPositive)) {
          large = false;
        } else {
          large = unifiedTheta &gt;= PI === !!clockwise;
        }

        x0 = round4(cx + rx * mathCos(theta));
        y0 = round4(cy + ry * mathSin(theta)); // It will not draw if start point and end point are exactly the same
        // We need to shift the end point with a small value
        // FIXME A better way to draw circle ?

        if (isCircle) {
          if (clockwise) {
            dTheta = PI2 - 1e-4;
          } else {
            dTheta = -PI2 + 1e-4;
          }

          large = true;

          if (i === 9) {
            // Move to (x0, y0) only when CMD.A comes at the
            // first position of a shape.
            // For instance, when drawing a ring, CMD.A comes
            // after CMD.M, so it&#39;s unnecessary to move to
            // (x0, y0).
            str.push(&#39;M&#39;, x0, y0);
          }
        }

        x = round4(cx + rx * mathCos(theta + dTheta));
        y = round4(cy + ry * mathSin(theta + dTheta)); // FIXME Ellipse

        str.push(&#39;A&#39;, round4(rx), round4(ry), mathRound(psi * degree), +large, +clockwise, x, y);
        break;

      case CMD.Z:
        cmdStr = &#39;Z&#39;;
        break;

      case CMD.R:
        x = round4(data[i++]);
        y = round4(data[i++]);
        w = round4(data[i++]);
        h = round4(data[i++]);
        str.push(&#39;M&#39;, x, y, &#39;L&#39;, x + w, y, &#39;L&#39;, x + w, y + h, &#39;L&#39;, x, y + h, &#39;L&#39;, x, y);
        break;
    }

    cmdStr &amp;&amp; str.push(cmdStr);

    for (var j = 0; j &lt; nData; j++) {
      // PENDING With scale
      str.push(round4(data[i++]));
    }
  }

  return str.join(&#39; &#39;);
}
<span id='qrenderer-svg-SVGPath'>/**
</span> * @class qrenderer.svg.SVGPath
 * 
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */


var svgPath = {};

svgPath.render = function (el) {
  var style = el.style;
  var svgEl = el.__svgEl;

  if (!svgEl) {
    svgEl = createElement(&#39;path&#39;);
    el.__svgEl = svgEl;
  }

  if (!el.path) {
    el.createPathProxy();
  }

  var path = el.path;

  if (el.__dirtyPath) {
    path.beginPath();
    path.subPixelOptimize = false;
    el.buildPath(path, el.shape);
    el.__dirtyPath = false;
    var pathStr = pathDataToString(path);

    if (pathStr.indexOf(&#39;NaN&#39;) &lt; 0) {
      // Ignore illegal path, which may happen such in out-of-range
      // data in Calendar series.
      attr(svgEl, &#39;d&#39;, pathStr);
    }
  }

  bindStyle(svgEl, style, false, el);
  applyTransform(svgEl, el.transform);

  if (style.text != null) {
    svgTextDrawRectText(el, el.getBoundingRect());
  } else {
    removeOldTextNode(el);
  }
};
<span id='qrenderer-svg-SVGImage'>/**
</span> * @class qrenderer.svg.SVGImage
 * 
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */


var svgImage = {};

svgImage.render = function (el) {
  var style = el.style;
  var image = style.image;

  if (image instanceof HTMLImageElement) {
    var src = image.src;
    image = src;
  }

  if (!image) {
    return;
  }

  var x = style.x || 0;
  var y = style.y || 0;
  var dw = style.width;
  var dh = style.height;
  var svgEl = el.__svgEl;

  if (!svgEl) {
    svgEl = createElement(&#39;image&#39;);
    el.__svgEl = svgEl;
  }

  if (image !== el.__imageSrc) {
    attrXLink(svgEl, &#39;href&#39;, image); // Caching image src

    el.__imageSrc = image;
  }

  attr(svgEl, &#39;width&#39;, dw);
  attr(svgEl, &#39;height&#39;, dh);
  attr(svgEl, &#39;x&#39;, x);
  attr(svgEl, &#39;y&#39;, y);
  applyTransform(svgEl, el.transform);

  if (style.text != null) {
    svgTextDrawRectText(el, el.getBoundingRect());
  } else {
    removeOldTextNode(el);
  }
};
<span id='qrenderer-svg-SVGText'>/**
</span> * @class qrenderer.svg.SVGText
 * 
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */


var svgText = {};

var _tmpTextHostRect = new BoundingRect();

var _tmpTextBoxPos = {};
var _tmpTextTransform = [];
var TEXT_ALIGN_TO_ANCHRO = {
  left: &#39;start&#39;,
  right: &#39;end&#39;,
  center: &#39;middle&#39;,
  middle: &#39;middle&#39;
};
<span id='qrenderer-svg-SVGText-method-svgTextDrawRectText'>/**
</span> * @param {Element} el
 * @param {Object|boolean} [hostRect] {x, y, width, height}
 *        If set false, rect text is not used.
 */

var svgTextDrawRectText = function svgTextDrawRectText(el, hostRect) {
  var style = el.style;
  var elTransform = el.transform;
  var needTransformTextByHostEl = el instanceof Text || style.transformText;
  el.__dirty &amp;&amp; textUtil.normalizeTextStyle(style, true);
  var text = style.text; // Convert to string

  text != null &amp;&amp; (text += &#39;&#39;);

  if (!textUtil.needDrawText(text, style)) {
    return;
  } // render empty text for svg if no text but need draw text.


  text == null &amp;&amp; (text = &#39;&#39;); // Follow the setting in the canvas renderer, if not transform the
  // text, transform the hostRect, by which the text is located.

  if (!needTransformTextByHostEl &amp;&amp; elTransform) {
    _tmpTextHostRect.copy(hostRect);

    _tmpTextHostRect.applyTransform(elTransform);

    hostRect = _tmpTextHostRect;
  }

  var textSvgEl = el.__textSvgEl;

  if (!textSvgEl) {
    textSvgEl = createElement(&#39;text&#39;);
    el.__textSvgEl = textSvgEl;
  } // style.font has been normalized by `normalizeTextStyle`.


  var textSvgElStyle = textSvgEl.style;
  var font = style.font || textUtil.DEFAULT_FONT;
  var computedFont = textSvgEl.__computedFont;

  if (font !== textSvgEl.__styleFont) {
    textSvgElStyle.font = textSvgEl.__styleFont = font; // The computedFont might not be the orginal font if it is illegal font.

    computedFont = textSvgEl.__computedFont = textSvgElStyle.font;
  }

  var textPadding = style.textPadding;
  var textLineHeight = style.textLineHeight;
  var contentBlock = el.__textCotentBlock;

  if (!contentBlock || el.__dirtyText) {
    contentBlock = el.__textCotentBlock = textUtil.parsePlainText(text, computedFont, textPadding, textLineHeight, style.truncate);
  }

  var outerHeight = contentBlock.outerHeight;
  var lineHeight = contentBlock.lineHeight;
  textUtil.getBoxPosition(_tmpTextBoxPos, el, style, hostRect);
  var baseX = _tmpTextBoxPos.baseX;
  var baseY = _tmpTextBoxPos.baseY;
  var textAlign = _tmpTextBoxPos.textAlign || &#39;left&#39;;
  var textVerticalAlign = _tmpTextBoxPos.textVerticalAlign;
  setTextTransform(textSvgEl, needTransformTextByHostEl, elTransform, style, hostRect, baseX, baseY);
  var boxY = textUtil.adjustTextY(baseY, outerHeight, textVerticalAlign);
  var textX = baseX;
  var textY = boxY; // TODO needDrawBg

  if (textPadding) {
    textX = getTextXForPadding(baseX, textAlign, textPadding);
    textY += textPadding[0];
  } // `textBaseline` is set as &#39;middle&#39;.


  textY += lineHeight / 2;
  bindStyle(textSvgEl, style, true, el); // FIXME
  // Add a &lt;style&gt; to reset all of the text font as inherit?
  // otherwise the outer &lt;style&gt; may set the unexpected style.
  // Font may affect position of each tspan elements

  var canCacheByTextString = contentBlock.canCacheByTextString;
  var tspanList = el.__tspanList || (el.__tspanList = []);
  var tspanOriginLen = tspanList.length; // Optimize for most cases, just compare text string to determine change.

  if (canCacheByTextString &amp;&amp; el.__canCacheByTextString &amp;&amp; el.__text === text) {
    if (el.__dirtyText &amp;&amp; tspanOriginLen) {
      for (var idx = 0; idx &lt; tspanOriginLen; ++idx) {
        updateTextLocation(tspanList[idx], textAlign, textX, textY + idx * lineHeight);
      }
    }
  } else {
    el.__text = text;
    el.__canCacheByTextString = canCacheByTextString;
    var textLines = contentBlock.lines;
    var nTextLines = textLines.length;
    var _idx = 0;

    for (; _idx &lt; nTextLines; _idx++) {
      // Using cached tspan elements
      var tspan = tspanList[_idx];
      var singleLineText = textLines[_idx];

      if (!tspan) {
        tspan = tspanList[_idx] = createElement(&#39;tspan&#39;);
        textSvgEl.appendChild(tspan);
        tspan.appendChild(document.createTextNode(singleLineText));
      } else if (tspan.__qrText !== singleLineText) {
        tspan.innerHTML = &#39;&#39;;
        tspan.appendChild(document.createTextNode(singleLineText));
      }

      updateTextLocation(tspan, textAlign, textX, textY + _idx * lineHeight);
    } // Remove unused tspan elements


    if (tspanOriginLen &gt; nTextLines) {
      for (; _idx &lt; tspanOriginLen; _idx++) {
        textSvgEl.removeChild(tspanList[_idx]);
      }

      tspanList.length = nTextLines;
    }
  }
};

function setTextTransform(textSvgEl, needTransformTextByHostEl, elTransform, style, hostRect, baseX, baseY) {
  matrixUtil.identity(_tmpTextTransform);

  if (needTransformTextByHostEl &amp;&amp; elTransform) {
    matrixUtil.copy(_tmpTextTransform, elTransform);
  } // textRotation only apply in RectText.


  var textRotation = style.textRotation;

  if (hostRect &amp;&amp; textRotation) {
    var origin = style.textOrigin;

    if (origin === &#39;center&#39;) {
      baseX = hostRect.width / 2 + hostRect.x1;
      baseY = hostRect.height / 2 + hostRect.y1;
    } else if (origin) {
      baseX = origin[0] + hostRect.x1;
      baseY = origin[1] + hostRect.y1;
    }

    _tmpTextTransform[4] -= baseX;
    _tmpTextTransform[5] -= baseY; // Positive: anticlockwise

    _tmpTextTransform = matrixUtil.rotate(_tmpTextTransform, textRotation);
    _tmpTextTransform[4] += baseX;
    _tmpTextTransform[5] += baseY;
  } // See the definition in `Style.js#textOrigin`, the default
  // origin is from the result of `getBoxPosition`.


  applyTransform(textSvgEl, _tmpTextTransform);
} // FIXME merge the same code with `helper/text.js#getTextXForPadding`;


function getTextXForPadding(x, textAlign, textPadding) {
  return textAlign === &#39;right&#39; ? x - textPadding[1] : textAlign === &#39;center&#39; ? x + textPadding[3] / 2 - textPadding[1] / 2 : x + textPadding[3];
}

function updateTextLocation(tspan, textAlign, x, y) {
  // Consider different font display differently in vertial align, we always
  // set vertialAlign as &#39;middle&#39;, and use &#39;y&#39; to locate text vertically.
  attr(tspan, &#39;dominant-baseline&#39;, &#39;middle&#39;);
  attr(tspan, &#39;text-anchor&#39;, TEXT_ALIGN_TO_ANCHRO[textAlign]);
  attr(tspan, &#39;x&#39;, x);
  attr(tspan, &#39;y&#39;, y);
}

function removeOldTextNode(el) {
  if (el &amp;&amp; el.__textSvgEl) {
    el.__textSvgEl.parentNode.removeChild(el.__textSvgEl);

    el.__textSvgEl = null;
    el.__tspanList = [];
    el.__text = null;
  }
}

svgText.drawRectText = svgTextDrawRectText;

svgText.render = function (el) {
  var style = el.style;

  if (style.text != null) {
    svgTextDrawRectText(el, false);
  } else {
    removeOldTextNode(el);
  }
};

exports.path = svgPath;
exports.image = svgImage;
exports.text = svgText;</pre>
</body>
</html>
